/**
 * 微信公众平台开发模式(JAVA) SDK
 */
package com.shixu.framework.plugins.weixin;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import net.sf.json.JSONObject;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.shixu.framework.core.AppContext;
import com.shixu.framework.plugins.weixin.bean.AccessToken;
import com.shixu.framework.plugins.weixin.bean.ApiTicket;
import com.shixu.framework.plugins.weixin.bean.Articles;
import com.shixu.framework.plugins.weixin.bean.Attachment;
import com.shixu.framework.plugins.weixin.bean.InMessage;
import com.shixu.framework.plugins.weixin.bean.JsapiTicket;
import com.shixu.framework.plugins.weixin.bean.OutMessage;
import com.shixu.framework.plugins.weixin.inf.MessageProcessingHandler;
import com.shixu.framework.plugins.weixin.service.ArticleService;
import com.shixu.framework.plugins.weixin.service.BulkMsgService;
import com.shixu.framework.plugins.weixin.service.GroupService;
import com.shixu.framework.plugins.weixin.service.MediaService;
import com.shixu.framework.plugins.weixin.service.MenuService;
import com.shixu.framework.plugins.weixin.service.MessageService;
import com.shixu.framework.plugins.weixin.service.QrcodService;
import com.shixu.framework.plugins.weixin.service.UserService;
import com.shixu.framework.plugins.weixin.util.HttpKit;
import com.shixu.framework.plugins.weixin.util.Tools;
import com.shixu.framework.plugins.weixin.util.WeixinConstant;
import com.shixu.framework.plugins.weixin.util.XStreamFactory;
import com.shixu.framework.util.MapUtils;
import com.thoughtworks.xstream.XStream;

/**
 * 微信常用的API
 *
 */
public class WeChat {
	
	private static Logger log = LoggerFactory.getLogger(WeChat.class);  
    
    private static Class<?>  messageProcessingHandlerClazz = null;
    /**
     * 消息操作接口
     */
    public static final MessageService message = new MessageService();
    /**
     * 菜单操作接口
     */
    public static final MenuService menu = new MenuService();
    /**
     * 用户操作接口
     */
    public static final UserService user = new UserService();
    /**
     * 分组操作接口
     */
    public static final GroupService group = new GroupService();
    
    /**
     * 二维码接口
     */
    public static final QrcodService qrcod = new QrcodService();
    
    /**
     * 上传下载多媒体文件接口
     */
    public static final MediaService media = new MediaService();
    
    /**
     * 上传图文消息素材接口
     */
    public static final ArticleService articles = new ArticleService();
    
    /**
     * 高级群发消息接口
     */
    public static final BulkMsgService bulkMessage = new BulkMsgService();
    
    private static AccessToken token= null;
    private static JsapiTicket ticket= null;
    private static ApiTicket apiTicket= null;
    

    /**
     * 获取access_token
     * @return
     * @throws Exception
     */
//    public static AccessToken getAccessToken() throws Exception {
//    	
//    	if(token!=null && !token.isExpired()){
//    		log.info("access_token 未失效-----------------");
//    		log.info("token====="+token.getToken()+"=================isExpired:"+token.isExpired());
//    		return token;
//    	}
//    	log.info("+++++++++++++++++++++++++++++++++++++++token="+token);
//        String appid = AppContext.getInstance().getString("appid");
//        String secret = AppContext.getInstance().getString("appsecret");
//   
//        return getAccessToken(appid, secret);
//    }
    
    
    /**
     * 获取access_token
     * @return
     * @throws Exception
     */
    public static AccessToken getAccessToken(String appid,String secret) throws Exception {
    	
    	if(token!=null && !token.isExpired()){
    		log.info("access_token 未失效-----------------");
    		log.info("token====="+token.getToken()+"=================isExpired:"+token.isExpired());
    		return token;
    	}
    	
    	log.info("appid:" + appid);
    	log.info("secret:" + secret);
    	
        String jsonStr = HttpKit.get(WeixinConstant.ACCESSTOKEN_URL.concat("&appid=") + appid + "&secret=" + secret);
        log.info("access_token_result:"+jsonStr);
        JSONObject map = JSONObject.fromObject(jsonStr);
        
        AccessToken accessToken = new AccessToken();
		
        if(map.has("errcode")){
        	log.error("获取access token失败："+ jsonStr);
        	return accessToken;
        }
		accessToken.setToken(map.getString("access_token"));
		accessToken.setExpiresIn(map.getInt("expires_in"));
		accessToken.setUpdateTime(new Date().getTime());
		log.info("weixin获取access_token成功，有效时长{}秒 token:{}", accessToken.getExpiresIn(), accessToken.getToken());
		token = accessToken;
        return accessToken;
    }
    
    /**
     * 获取jsapi_ticket
     * @return
     * @throws Exception
     */
    public static JsapiTicket getJsapiTicket(String appid,String secret) throws Exception {
    	
    	if(ticket!=null && !ticket.isExpired()){
    		log.info("jsapi_ticket 未失效-----------------");
    		log.info("ticket====="+ticket.getTicket()+"=================isExpired:"+ticket.isExpired());
    		return ticket;
    	}
    	
    	String accessToken = getAccessToken(appid,secret).getToken();
    	
    	JsapiTicket jsapiTicket=getJsapiTicket(accessToken);
    	log.info("+++++++++++++++++++++++++++++++++++++++ticket="+jsapiTicket.getTicket());
        return jsapiTicket;
    }
    
    
    /**
     * 获取jsapi_ticket
     * @return
     * @throws Exception
     */
    public static JsapiTicket getJsapiTicket(String accessToken) throws Exception {
    	
        String jsonStr = HttpKit.get(WeixinConstant.jsapi_ticket_url.concat(accessToken));
        log.info("jsapi_ticket_result:"+jsonStr);
        JSONObject map = JSONObject.fromObject(jsonStr);
        
        JsapiTicket jsapiTicket = new JsapiTicket();
		
        if(MapUtils.getInteger(map, "errcode")!=0){
        	log.error("获取jsapi_ticket失败："+ jsonStr);
        	return jsapiTicket;
        }
        jsapiTicket.setTicket(map.getString("ticket"));
        jsapiTicket.setExpiresIn(map.getInt("expires_in"));
        jsapiTicket.setUpdateTime(new Date().getTime());
		log.info("weixin获取jsapi_ticket成功，有效时长{}秒 token:{}", jsapiTicket.getExpiresIn(), jsapiTicket.getTicket());
		ticket = jsapiTicket;
        return jsapiTicket;
    }
    
    /**
     * 获取api_ticket
     * @return
     * @throws Exception
     */
    public static ApiTicket getApiTicket(String appid,String secret) throws Exception {
    	
    	if(apiTicket!=null && !apiTicket.isExpired()){
    		log.info("apiTicket 未失效-----------------");
    		log.info("apiTicket====="+apiTicket.getTicket()+"=================isExpired:"+apiTicket.isExpired());
    		return apiTicket;
    	}
    	
    	String accessToken = getAccessToken(appid,secret).getToken();
    	
		ApiTicket aTicket = getApiTicket(accessToken);
		log.info("+++++++++++++++++++++++++++++++++++++++apiTicket=" + aTicket.getTicket());
		return aTicket;
    }
    
    
    /**
     * 获取api_ticket
     * @return
     * @throws Exception
     */
    public static ApiTicket getApiTicket(String accessToken) throws Exception {
    	
        String jsonStr = HttpKit.get(WeixinConstant.api_ticket_url.concat(accessToken));
        log.info("api_ticket_result:"+jsonStr);
        JSONObject map = JSONObject.fromObject(jsonStr);
        
        ApiTicket aTicket = new ApiTicket();
		
        if(MapUtils.getInteger(map, "errcode")!=0){
        	log.error("获取apiTicket失败："+ jsonStr);
        	return aTicket;
        }
        aTicket.setTicket(map.getString("ticket"));
        aTicket.setExpiresIn(map.getInt("expires_in"));
        aTicket.setUpdateTime(new Date().getTime());
		log.info("weixin获取apiTicket成功，有效时长{}秒 token:{}", aTicket.getExpiresIn(), aTicket.getTicket());
		apiTicket = aTicket;
        return aTicket;
    }

   /**
    * 支付反馈
    * @param openid
    * @param feedbackid
    * @return
    * @throws Exception
    */
    public static boolean payfeedback(String openid, String feedbackid,String appid,String secret) throws Exception {
        Map<String, String> map = new HashMap<String, String>();
        
        map.put("access_token", getAccessToken(appid,secret).getToken());
        map.put("openid", openid);
        map.put("feedbackid", feedbackid);
        String jsonStr = HttpKit.get(WeixinConstant.PAYFEEDBACK_URL, map);
        JSONObject jsonMap = JSONObject.fromObject(jsonStr);
        return "0".equals(jsonMap.getString("errcode"));
    }

    /**
     * 签名检查
     * @param token
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     */
    public static Boolean checkSignature(String token, String signature, String timestamp, String nonce) {
        return Tools.checkSignature(token, signature, timestamp, nonce);
    }
    
    /**
     * 根据接收到用户消息进行处理
     * @param responseInputString 微信发送过来的xml消息体
     * @return
     */
    public static String processing(String responseInputString) {
    	String handler = AppContext.getInstance().getString("MessageProcessingHandlerImpl");
    	return processing(responseInputString, handler);   
    }

    /**
     * 根据接收到用户消息进行处理
     * @param responseInputString   微信发送过来的xml消息体
     * @param handler 自定义消息处理器的包名及文件名（如：com.shixu.xylive.util.WeixinCallbackHandler）
     * @return
     */
    public static String processing(String responseInputString,String handler) {
    	
    	log.debug("#################  message procession.......... ##################");
        InMessage inMessage = parsingInMessage(responseInputString);
        OutMessage oms = null;
        // 加载处理器
        if (messageProcessingHandlerClazz == null) {
            // 获取自定消息处理器，如果自定义处理器则使用默认处理器。
//            String handler = AppContext.getInstance().getString("MessageProcessingHandlerImpl");
            handler = handler == null ? WeixinConstant.DEFAULT_HANDLER : handler;
            try {
            	messageProcessingHandlerClazz = Thread.currentThread().getContextClassLoader().loadClass(handler);
            } catch (Exception e) {
                throw new RuntimeException("messageProcessingHandler Load Error！");
            }
        }
        String xml = "";
        try {
        	MessageProcessingHandler messageProcessingHandler = (MessageProcessingHandler) messageProcessingHandlerClazz.newInstance();
            //取得消息类型
            String type = inMessage.getMsgType();
            Method getOutMessage = messageProcessingHandler.getClass().getMethod("getOutMessage");
            Method alMt = messageProcessingHandler.getClass().getMethod("allType", InMessage.class);
            Method mt = messageProcessingHandler.getClass().getMethod(type + "TypeMsg", InMessage.class);
            
            alMt.invoke(messageProcessingHandler, inMessage);
           
            if(mt != null){
            	mt.invoke(messageProcessingHandler, inMessage);
            }
            
            Object obj = getOutMessage.invoke(messageProcessingHandler);
            if(obj != null){
            	oms = (OutMessage) obj;
            }
            //调用事后处理
            try {
            	Method aftMt =  messageProcessingHandler.getClass().getMethod("afterProcess",InMessage.class,OutMessage.class);
            	aftMt.invoke(messageProcessingHandler, inMessage, oms);
			} catch (Exception e) {}
            
            obj = getOutMessage.invoke(messageProcessingHandler);
            if(obj != null){
            	oms = (OutMessage) obj;
            	setMsgInfo(oms, inMessage);
            }
        } catch (Exception e) {
        	throw new RuntimeException(e);
        }
       
        if(oms != null){
            // 把发送发送对象转换为xml输出
            XStream xs = XStreamFactory.init(true);
            xs.alias("xml", oms.getClass());
            xs.alias("item", Articles.class);
            xml = xs.toXML(oms);
        }
        log.debug("############## procession end ###################");
        return xml;
    }

    /**
     * 设置发送消息体
     * @param oms
     * @param msg
     * @throws Exception
     */
    private static void setMsgInfo(OutMessage oms,InMessage msg) throws Exception {
    	if(oms != null){
    		Class<?> outMsg = oms.getClass().getSuperclass();
            Field CreateTime = outMsg.getDeclaredField("CreateTime");
            Field ToUserName = outMsg.getDeclaredField("ToUserName");
            Field FromUserName = outMsg.getDeclaredField("FromUserName");

            ToUserName.setAccessible(true);
            CreateTime.setAccessible(true);
            FromUserName.setAccessible(true);

            CreateTime.set(oms, new Date().getTime());
            ToUserName.set(oms, msg.getFromUserName());
            FromUserName.set(oms, msg.getToUserName());
    	}
    }

    /**
     *消息体转换
     * @param responseInputString
     * @return
     */
    private static InMessage parsingInMessage(String responseInputString) {
        //转换微信post过来的xml内容
        XStream xs = XStreamFactory.init(false);
        xs.ignoreUnknownElements();
        xs.alias("xml", InMessage.class);
        InMessage msg = (InMessage) xs.fromXML(responseInputString);
        return msg;
    }
    
    /**
     * 获取媒体资源
     * @param accessToken
     * @param mediaId
     * @return
     * @throws IOException
     * @throws InterruptedException 
     * @throws ExecutionException 
     */
    public static Attachment getMedia(String accessToken,String mediaId) throws IOException, ExecutionException, InterruptedException{
    	String url = WeixinConstant.GET_MEDIA_URL + accessToken + "&media_id=" + mediaId;
        return HttpKit.download(url);
    }
    
    /**
     * 上传素材文件
     * @param type
     * @param file
     * @return
     * @throws KeyManagementException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws IOException
     * @throws InterruptedException 
     * @throws ExecutionException 
     */
    @SuppressWarnings("unchecked")
	public static Map<String, Object> uploadMedia(String accessToken,String type,File file) throws KeyManagementException, NoSuchAlgorithmException, NoSuchProviderException, IOException, ExecutionException, InterruptedException {
        String url = WeixinConstant.UPLOAD_MEDIA_URL + accessToken +"&type="+type;
        String jsonStr = HttpKit.upload(url,file);
        return JSONObject.fromObject(jsonStr);
    }
    
    /**
     * 判断是否来自微信, 5.0 之后的支持微信支付
     * @param request
     * @return
     */
 	public static boolean isWeiXin(HttpServletRequest request) {
 		String userAgent = request.getHeader("User-Agent");
 		if (StringUtils.isNotBlank(userAgent)) {
 			Pattern p = Pattern.compile("MicroMessenger/(\\d+).+");
 			Matcher m = p.matcher(userAgent);
 			String version = null;
 			if(m.find()){
 				version = m.group(1);
 			}
 			return (null != version && NumberUtils.toInt(version) >= 5); 
 		}
 		return false;
 	}
 	
 	/**
 	 * 获取短地址
 	 * @param longUrl
 	 * @return
 	 * @throws Exception
 	 */
 	public static String shortUrl(String longUrl) throws Exception{
		
 		return longUrl;
// 		
// 		Map<String ,String> param = new HashMap<String,String>();
//
// 		param.put("url", longUrl);
// 		param.put("callback", "call");
// 		String res = HttpKit.get(WeixinConstant.SHORT_URL, param);
//
// 		Pattern p = Pattern.compile("\\{.*\\}");
//		Matcher m = p.matcher(res);
//		
//		m.find();
//		String json = m.group();
//		
// 		JSONObject obj = JSONObject.fromObject(json);
// 		if("error".equals(obj.getString("status"))){
// 			log.error("########### long2short ################");
// 			log.error("error code:"+obj.getString("status"));
// 			log.error("error msg:"+obj.getString("err_msg"));
// 			log.error("########### long2short ################");
// 			return null;
// 		}
// 		return obj.getString("msg");
 	}

}
